/* Copyright (c) 2016-2017, ARM Limited and Contributors
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge,
 * to any person obtaining a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "assets.hpp"
#include "common.hpp"
#include "platform/os.hpp"
#include "platform/platform.hpp"
#include <stdio.h>
#include <vector>

#define STB_IMAGE_STATIC
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>

using namespace std;

namespace MaliSDK
{

VkShaderModule loadShaderModule(VkDevice device, const char *pPath)
{
	vector<uint32_t> buffer;
	if (FAILED(OS::getAssetManager().readBinaryFile(&buffer, pPath)))
	{
		LOGE("Failed to read SPIR-V file: %s.\n", pPath);
		return VK_NULL_HANDLE;
	}

	VkShaderModuleCreateInfo moduleInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
	moduleInfo.codeSize = buffer.size() * sizeof(uint32_t);
	moduleInfo.pCode = buffer.data();

	VkShaderModule shaderModule;
	VK_CHECK(vkCreateShaderModule(device, &moduleInfo, nullptr, &shaderModule));
	return shaderModule;
}

Result loadRgba8888TextureFromAsset(const char *pPath, vector<uint8_t> *pBuffer, unsigned *pWidth, unsigned *pHeight)
{
	vector<uint8_t> compressed;
	if (FAILED(OS::getAssetManager().readBinaryFile(&compressed, pPath)))
	{
		LOGE("Failed to read texture: %s.\n", pPath);
		return RESULT_ERROR_IO;
	}

	int x, y, comp;

	uint8_t *pResult = stbi_load_from_memory(compressed.data(), compressed.size(), &x, &y, &comp, STBI_rgb_alpha);
	if (!pResult || comp != STBI_rgb_alpha)
	{
		LOGE("Failed to decompress texture: %s.\n", pPath);
		free(pResult);
		return RESULT_ERROR_GENERIC;
	}

	pBuffer->clear();
	pBuffer->insert(end(*pBuffer), pResult, pResult + x * y * 4);
	*pWidth = x;
	*pHeight = y;
	free(pResult);

	return RESULT_SUCCESS;
}

/// Header for the on-disk format generated by astcenc.
struct ASTCHeader
{
	/// Magic value
	uint8_t magic[4];
	/// Block size in X
	uint8_t blockdimX;
	/// Block size in Y
	uint8_t blockdimY;
	/// Block size in Z
	uint8_t blockdimZ;
	/// Size of the image in pixels (X), least significant byte first.
	uint8_t xsize[3];
	/// Size of the image in pixels (Y), least significant byte first.
	uint8_t ysize[3];
	/// Size of the image in pixels (Z), least significant byte first.
	uint8_t zsize[3];
};
static_assert(sizeof(ASTCHeader) == 16, "Packed ASTC header struct is not 16 bytes.");

#define ASTC_MAGIC 0x5CA1AB13

Result loadASTCTextureFromAsset(const char *pPath, vector<uint8_t> *pBuffer, unsigned *pWidth, unsigned *pHeight,
                                VkFormat *pFormat)
{
	vector<uint8_t> compressed;
	if (FAILED(OS::getAssetManager().readBinaryFile(&compressed, pPath)))
	{
		LOGE("Failed to read ASTC texture: %s.\n", pPath);
		return RESULT_ERROR_IO;
	}

	if (compressed.size() < sizeof(ASTCHeader))
		return RESULT_ERROR_GENERIC;

	ASTCHeader header;
	memcpy(&header, compressed.data(), sizeof(ASTCHeader));
	uint32_t magic = header.magic[0] | (uint32_t(header.magic[1]) << 8) | (uint32_t(header.magic[2]) << 16) |
	                 (uint32_t(header.magic[3]) << 24);

	if (magic != ASTC_MAGIC)
	{
		LOGE("Texture %s is not ASTC.\n", pPath);
		return RESULT_ERROR_GENERIC;
	}

	if (header.blockdimZ != 1)
	{
		LOGE("ASTC 3D textures not supported yet in Vulkan.\n");
		return RESULT_ERROR_GENERIC;
	}

	if (header.blockdimX == 4 && header.blockdimY == 4) // 4x4
		*pFormat = VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
	else if (header.blockdimX == 5 && header.blockdimY == 4) // 5x4
		*pFormat = VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
	else if (header.blockdimX == 5 && header.blockdimY == 5) // 5x5
		*pFormat = VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
	else if (header.blockdimX == 6 && header.blockdimY == 5) // 6x5
		*pFormat = VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
	else if (header.blockdimX == 6 && header.blockdimY == 6) // 6x6
		*pFormat = VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
	else if (header.blockdimX == 8 && header.blockdimY == 5) // 8x5
		*pFormat = VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
	else if (header.blockdimX == 8 && header.blockdimY == 6) // 8x6
		*pFormat = VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
	else if (header.blockdimX == 8 && header.blockdimY == 8) // 8x8
		*pFormat = VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
	else if (header.blockdimX == 10 && header.blockdimY == 5) // 10x5
		*pFormat = VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
	else if (header.blockdimX == 10 && header.blockdimY == 6) // 10x6
		*pFormat = VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
	else if (header.blockdimX == 10 && header.blockdimY == 8) // 10x8
		*pFormat = VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
	else if (header.blockdimX == 10 && header.blockdimY == 10) // 10x10
		*pFormat = VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
	else if (header.blockdimX == 12 && header.blockdimY == 10) // 12x10
		*pFormat = VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
	else if (header.blockdimX == 12 && header.blockdimY == 12) // 12x12
		*pFormat = VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
	else
	{
		LOGE("Unknown ASTC block size %u x %u.\n", header.blockdimX, header.blockdimY);
		return RESULT_ERROR_GENERIC;
	}

	pBuffer->clear();
	pBuffer->insert(end(*pBuffer), begin(compressed) + sizeof(ASTCHeader), end(compressed));
	*pWidth = header.xsize[0] | (header.xsize[1] << 8) | (header.xsize[2] << 16);
	*pHeight = header.ysize[0] | (header.ysize[1] << 8) | (header.ysize[2] << 16);
	return RESULT_SUCCESS;
}
}
